其他
MYSQL基础知识及案例分析
3. 锁基本理论4. 索引的分类及使用
5. 案例分析
各种锁特点· 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生冲突的概率最高,并发度最低· 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高· 页面锁:开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般
行锁的唯一识别第一印象想到的是,用每行记录的键值来做行锁的唯一识别.但是键值占用空间比较大。InnoDB使用Page NO.+Heap NO.来做行锁的唯一识别。我们可以将Heap no.理解为页面上的一个自增数值。每条物理记录在被创建时,都会分配一个唯一的heap no. A. 键值可以理解为一个逻辑值,page no. + heap no. 是物理的。 B. 物理的虽然占用空间小,但是处理要复杂一些。如:在分裂一个B+Tree页面时,一半的记录要移到新的页面中,因此要对存在的锁进行迁移。
· InnoDB是通过在每行记录后面保存两个隐藏的列来实现的。一个保存行的创建时间,一个保存行的过期时间(或删除时间)。这个不是实际的时间值,而是系统版本号(System Version Number)。
· 每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的的系统版本号作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
· MVCC 只在RC和RR两个隔离级别下工作。这样设计使大部分读操作不用加锁,读和写不冲突。
Intention shared(IS): 意向共享锁。 SELECT ... LOCK IN SHARE MODE
Intention exclusive(IX):意向排他锁 SELECT ... FOR UPDATE
· 意向锁协议: A. 请求获取表t某一行的S锁之前,必须在表t上获取IS或者更强的锁; B. 请求获取某一行的X锁之前,必须在表上获取IX锁;· 意向锁主要用来表示某人正在锁定或者即将要锁定行记录
· 记录锁永远锁定的是索引,即使表没有索引。InnoDB会隐含创建一个聚簇索引,用这个索引进行记录锁。
· 间隙锁是性能和并发之间妥协的产物,可以通过设置事务隔离级别为 RC或者开启系统变量innodb_locks_unsafe_for_binlog(已过时),来禁用间隙锁
· 间隙锁在同一个gap上没有冲突一说,事务A在gap上有个S锁,事务B允许在同一个gap上持有X锁。 The reason conflicting gap locks are allowed is that if a record is purged from an index, the gap locks held on the record by different transactions must be merged.
· 间隙锁在Innodb是“纯抑制”,意思是它只阻止其他事务插入数据到这个间隙, 而不会阻止其他事务获取这个间隙上的间隙锁。因此间隙X锁与间隙S锁效果相同。
· 假设索引记录有值 10, 11, 13, 20, 那可能的next-key lock包括: (-∞, 10]
(10, 11]
(11, 13]
(13,20]
(20,+ ∞)
· Innodb默认就是在RR隔离级别下, 使用的next-key locks用来查询和索引扫描,阻止幻读的发生
· next-key locks 会降级(优化)为record locks: 当索引含有唯一约束时 锁定一条记录
· 由于事务的隔离性和一致性要求,会对所有扫描到的record加锁。比如:update ... where/delete .. where/select ...from...lock in share mode/ select .. from .. for update这都是next-key lock。
· 相对的还有 previous-key locks, 锁定区间与next-key 的开闭相反。
· 提高并发插入性能。假设索引记录有4,7,两个不同事务分别插入5,6, 每个事务都在区间4,7上先请求“插入意向锁”,在获取插入行的排他锁之前,这样就不会互相造成阻塞因为这两行不冲突
· 一种特殊的表锁,为了提高插入的性能,锁不是在一个事务完成后释放,在完成对自增长值的SQL语句后立即释放。并发插入场景,事务必须等待前一个插入的完成(虽然不用等待事务的完成)。所以有时候也会被称为“语句”级别的锁。
· 因为不是事务级别的锁,innodb的自增主键很有可能不是延续的。默认innodb_autoinc_lock_mode=1,可预判需要多少行,并一次性预生成。
· InnoDB引擎中,自增长的列必须是索引,同时必须是索引的第一列。如果不是Mysql会抛出异常。
· Innodb 实现了一个延迟加锁的机制,来减少加锁的数量,在代码中称为隐式锁(Implicit Lock)。隐式锁中有个重要的元素,事务ID(trx_id)。
· 隐式锁的特点A. 只有在很可能发生冲突时才加锁,减少了锁的数量。B. 隐式锁是针对被修改的B+Tree记录,因此都是Record类型的锁。不可能是Gap或Next-Key类型。
· 隐式锁的使用A. INSERT操作只加隐式锁,不需要显示加锁。B. UPDATE,DELETE在查询时,直接对查询用的Index和主键使用显示锁,其他索引上使用隐式锁。C. INSERT,UPDATE,DELETE对B+Tree们的操作都是从主键的B+Tree开始,因此对主键加锁可以有效的阻止死锁。
隐式锁的逻辑过程如下:A. InnoDB的每条记录中都一个隐含的trx_id字段,这个字段存在于簇索引的B+Tree中。B. 在操作一条记录前,首先根据记录中的trx_id检查该事务是否是活动的事务(未提交或回滚).如果是活动的事务,首先将隐式锁转换为显式锁(就是为该事务添加一个锁)。C. 检查是否有锁冲突,如果有冲突,创建锁,并设置为waiting状态。如果没有冲突不加锁,跳到E。D. 等待加锁成功,被唤醒,或者超时。E. 写数据,并将自己的trx_id写入trx_id字段。Page Lock可以保证操作的正确性。
· 在事务隔离级别RC和RR下,InnoDB存储引擎使用非锁定的一致性读。然而,对于快照数据的定义却不相同。在rc事务隔离级别下,对于快照数据,非一致性读总是被锁定行的最新一份快照数据。而在RR事务隔离级别下,对于快照数据,非一致性读总是读取事务开始时的行数据版本。
SELECT ... FOR UPDATE :锁定读取行以及关联的索引,与直接update这些行实现的效果一致。其他事务不能再更新这些行,也不能加S锁。
示例1:保证parent表有数据,child表才可以插入一条记录。S锁可以保证事务内查询存在的数据不会在 select和insert to child期间被其他事务删除。 SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;
示例2: 并发更新计数器 SELECT counter_field FROM child_codes FOR UPDATE; //排他锁 UPDATE child_codes SET counter_field = counter_field + 1; LAST_INSERT_ID(): 在当前connection上下文内,最近一次操作insert或update影响自增列的最终ID UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1); SELECT LAST_INSERT_ID();
· B-Tree索引对索引列是顺序组织存储的,所以适合范围查找。适用于全键值、键值范围或键前缀查找。启动键前缀查找只适用于根据最左前缀的查找。
· 哈希索引基于哈希表实现,只有精准匹配索引所有列的查询才有效。哈希表中保存指向每个数据行的指针。是Memory引擎的默认索引类型。限制有:只包含哈希和行指针,不存储字段值;不是按照索引列的值顺序存储的,无法用于排序;不支持部分索引列匹配查找,因为哈希索引始终使用索引列的全部内容来计算哈希值的;只支持等值比较查找不支持范围查找;哈希冲突问题;
· 空间数据索引(R-Tree): MyISAM表支持空间索引,可以用作地理数据存储。不常用
· 全文索引是一种特殊类型索引,查找的是文本中的关键词,而不是直接比较索引中的值。
二星:索引中的数据顺序和查找中的排列顺序一致;
三星:索引中的列包含了查询中需要的全部列;
· 独立的列:指索引列不能使表达式的一部分,也不能是函数的参数。· 前缀索引和索引选择性:对于text或者很长的varchar类型的列,需要索引开始的部分字符,可以大大结余索引空间,MySQL也不允许索引这些列的完整长度。诀窍在于要选择足够长的前缀以保证较高的选择性。· 多列索引:当多个索引做相交操作时(AND条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引。当多个索引做联合操作时(OR条件),需要耗费大量的CPU和内存在算法的缓存、排序和合并操作上,而优化器不会把这些计算到“查询成本”(COST)中, 优化器只关心随机页面读取。有些时候还不如改成UNION的方式更好。
· 无法同时把数据行放在两个不同的地方,所以一个表只能有一个聚簇索引。优点:数据集中,减少磁盘IO;数据访问快;缺点:插入速度严重依赖于插入顺序(自增长, 避免UUID),按照主键的顺序插入式加载数据到InnoDB表中速度最快的方式;可能面临页分裂(page split)问题; 二级索引访问需要两次索引查找(二级索引的叶子节点保存的是行的主键值,不是行记录物理位置的指针);
· 题外:顺序的主键什么时候回造成更坏的结果?对于高并发,主键的上界会成为“热点”,因为所有的插入都发生在这里,所以并发插入可能导致间隙锁竞争。另一个热点可能是 AUTO-INC锁机制。考虑更改 innodb_autoinc_lock_mode参数配置
· 索引条目远小于数据行的大小,如果只需要读取索引,会极大减少数据访问量
· 索引是按照列值顺序存储的,所以对于IO密集型的范围查询会比随机从磁盘读取每一行数据的IO要少得多
· InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询。
2. 一个锁定记录集的事务,其操作结果集应尽量简短,以免一次占用太多资源,与其他事务处理的记录冲突。
3.更新或者删除表格数据,sql语句的where条件都是主键或都是索引,避免两种情况交叉,造成死锁。对于where子句较复杂的情况,将其单独通过sql得到后,再在更新语句中使用。
4. sql语句的嵌套表格不要太多,能拆分就拆分,避免占有资源同时等待资源,导致与其他事务冲突。
5. 对定点运行脚本的情况,避免在同一时间点运行多个对同一表进行读写的脚本,特别注意加锁且操作数据量比较大的语句。
6.应用程序中增加对死锁的判断,如果事务意外结束,重新运行该事务,减少对功能的影响。